home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Libris Britannia 4
/
science library(b).zip
/
science library(b)
/
DJGPP
/
AETSK102.ZIP
/
contrib
/
tasks
/
src
/
task.cc
< prev
next >
Wrap
C/C++ Source or Header
|
1991-12-01
|
15KB
|
458 lines
/**********************************************************************
*
* NAME: task.cpp
*
* DESCRIPTION: routines for multitasking scheduler
*
* copyright (c) 1991 J. Alan Eldridge
*
* M O D I F I C A T I O N H I S T O R Y
*
* when who what
* -------------------------------------------------------------------
* 03/12/91 J. Alan Eldridge created
*
* Mar-May 91 JAE many, many revisions
*
* 10/23/91 JAE added code to force task stack size
* up to reasonable minimum value
*
* 10/26/91 JAE added support for DJ Delorie's port
* port of GNU C++ v. 1.39
*
* 11/09/91 JAE rewrote to use SysQP class for
* Sema, Task queues
*
* tasks now can have priorities...
* (be careful of starvation!)
*
* 11/11/91 JAE added stack basher checking in class
* Task, called by scheduler
*
* 11/13/91 JAE moved semas to their own file
*
* 11/30/91 jae added fDelete flag to Task, so you can
* dynamically create a new Task on the heap,
* and then proceed, knowing that the storage
* will eventually be freed when the task
* is killed or commits sucicide ...
*
* added new queue pZombies, and code in
* Task::suicide() and scheduer() to support
* this feature
*
* added CHECK_... defines to control
* run-time checking for fatal errors
* (calling TaskFatal() if necessary)
*
*********************************************************************/
#include "aedef.h"
#include "task.h"
//------------------------------------------------------------
// local defines to control error checking/reporting:
// each of these enables to code to check for an error condition
// and call TaskFatal() with a message if error occurred
//------------------------------------------------------------
#define CHECK_SUSPEND 1 // set to 1 to check for attempt to
// suspend non-current task
#define CHECK_STACK_OVER 1 // set to 1 to check for stack overflow
#define CHECK_STACK_ALLOC 1 // set to 1 to check for failed attempt
// to allocate a Task's stack
#define CHECK_QUEUE_ALLOC 1 // set to 1 to check for failure to create
// the three system task queues
//------------------------------------------------------------
// copyright notice: do not remove this definition!
//------------------------------------------------------------
static const char copyright[] = "Task++ v. " TASKPP_VERSION
" Copyright (c) 1991 J. Alan Eldridge";
//------------------------------------------------------------
// Stack fill value
//------------------------------------------------------------
const uchar Task::stkval = 0xAE;
//------------------------------------------------------------
// ********** SCHEDULER **********
//------------------------------------------------------------
//------------------------------------------------------------
// the scheduler's data storage is implemented entirely
// by these static variables
//------------------------------------------------------------
// static SLList tasks; // list of Tasks
// This variable "tasks" has been changed to a ptr to a
// list of tasks. This was done at the suggestion of Peter W.
// at Borland Tech Support. Here's the scenario, since
// it is of interest to anyone creating global objects:
//
// The order of calling global object constructors inside one
// module is well defined: top to bottom, left to right.
// The order of calling the constructors in multiple modules
// is "implementation-defined" and subject to change without
// notice. That is, I cannot ensure that the constructor for
// "tasks" is called before the constructors for any global Task
// instances. The only way I can control when the constructor
// is called is to allocate the list using operator new. Thus,
// the ptr is initialized to 0 (explicitly, since we need to
// make sure it is done before the startup code calls constructors
// and zeros uninitialized globals). Then, the code to install
// a task in the list (sch_AddTask()) checks the value, and
// creates the list if it doesn't already exist. There is the
// slight inefficiency caused by the extra level of indirection,
// but this is the only reliable way to get the results I need.
static SysPriQP *pRunning = 0; // ptr to list of running Tasks
static SysQP *pBlocked = 0; // ptr to list of blocked Tasks
static SysQP *pZombies = 0; // ptr to list of zombie Tasks
// (scheduler will kill them...)
static jmp_buf schEnv; // environment in scheduler
//------------------------------------------------------------
// the global CurrTask lets an application function know
// who's currently executing
//------------------------------------------------------------
Task *CurrTask = 0;
//------------------------------------------------------------
// these 4 calls are the entire interface to the scheduler:
// SchAddTask(), SchResume(), SchWakeup(), scheduler()
//------------------------------------------------------------
//------------------------------------------------------------
// SchAddTask -- add a new task to the scheduler task list
// WARNING: this should only be called by class Task constructor
//------------------------------------------------------------
inline void
SchAddTask(Task *t)
{
if (!pRunning) {
pRunning = new SysPriQP;
pBlocked = new SysQP;
pZombies = new SysQP;
#if CHECK_QUEUE_ALLOC
if (!(pRunning && pBlocked && pZombies))
TaskFatal("can't create task queues!");
#endif
}
pRunning->Add(t);
}
//------------------------------------------------------------
// SchResume -- return to setjmp() in scheduler()
//------------------------------------------------------------
inline void
SchResume(int code = 1)
{
longjmp(schEnv, code);
}
//------------------------------------------------------------
// SchWakeup() -- wakeup any sleeping or blocked tasks
//------------------------------------------------------------
inline void
SchWakeup()
{
int cnt = pBlocked->Cnt();
while (cnt-- > 0) {
Task *pt = GetNxtTask(*pBlocked);
if (pt->maybeWake())
pRunning->Add(pt);
else
pBlocked->Add(pt);
}
}
//------------------------------------------------------------
// scheduler() -- this is the main scheduler loop
//
// if the arg is 0, it means somebody wants to shut
// down all tasks... we do so, then resume the scheduler
// at the setjmp(), where it will return on its own stack
//
// otherwise... every time a task surrenders control it
// returns to the setjmp() near the beginning. we then:
//
// check for stack overflow
// if it's not a zombie...
// place it on the right queue (ready or blocked)
//
// then we go into a simple loop:
//
// while (there are tasks left) loop
// wakeup anybody who can be awakened
// get next one off ready queue
// if nobody ready, continue
// if it hasn't been initialized,
// initialize it
// else
// make it return to where it suspended itself
// end loop
//
// returns 1 if forced down by scheduler(0), zero
// if tasks terminated normally, and optionally calls TaskFatal()
// if stack basher has been detected
//
//------------------------------------------------------------
int
scheduler(int tasking)
{
if (!tasking) {
CurrTask = 0;
SchResume(-1);
}
int code = setjmp(schEnv);
// check state of CurrTask and put on a task queue
if (code >= 0 && CurrTask) {
#if CHECK_STACK_OVER
if (!CurrTask->stackok()) {
// die if we blew out the stack
TaskFatal("stack overflow!");
} else
#endif
{
// add to appropriate queue
if (CurrTask->isReady())
pRunning->Add(CurrTask);
else if (!CurrTask->isZombie())
pBlocked->Add(CurrTask);
else
pZombies->Add(CurrTask);
}
}
// clean up any zombies left around
int zcnt = pZombies->Cnt();
while (zcnt-- > 0) {
Task *pt = GetNxtTask(*pZombies);
if (pt->shouldDelete())
delete pt;
}
// find next eligible task to run and set it off
while (code >= 0 && (pRunning->Cnt() > 0 || pBlocked->Cnt() > 0)) {
SchWakeup();
if (!(CurrTask = GetNxtTask(*pRunning)))
continue;
if (!CurrTask->isInited())
CurrTask->init();
else
CurrTask->resume(CurrTask->timeOut() ? -1 : 1);
}
CurrTask = 0;
return code < 0;
}
//------------------------------------------------------------
// ********** CLASS TASK MEMBER FUNCTIONS **********
//------------------------------------------------------------
//------------------------------------------------------------
// constructor for a Task: set flags, allocate stack space
//------------------------------------------------------------
Task::Task(char *tname, int stk): stklen(stk), tskname(tname)
{
fInited = 0;
fReady = 1;
fTimed = 0;
fZombie = 0;
fDelete = 0;
tskpri = 1;
if (stk < StackMin)
stk = StackMin;
stack = new uchar [ stk ];
#if CHECK_STACK_ALLOC
if (!stack)
TaskFatal("can't allocate stack for task %s!", tname);
#endif
memset(stack, stkval, stk);
SchAddTask(this);
}
//------------------------------------------------------------
// Task::timeOut() -- return & reset timed out flag
//------------------------------------------------------------
int
Task::timeOut()
{
int timedOut = fTimed;
fTimed = 0;
return timedOut;
}
//------------------------------------------------------------
// Task::init() -- initialize a task: switch stacks and
// call the virtual TaskMain() function, never to return
//------------------------------------------------------------
// this function is used as a wrapper so that the "this"
// pointer is saved on the stack; that way, if a task returns,
// it immediately commits suicide
static void
callTaskMain(Task *t)
{
t->TaskMain(); // call the main function ...
t->suicide(); // if it returns, the task wants to die
}
void
Task::init()
{
fInited = 1; // we are initialized
// switch to private stack now
static jmp_buf stkswitch;
if (!setjmp(stkswitch)) {
#if defined(__TURBOC__)
uchar far *farstk = (uchar far *)stack;
stkswitch[ 0 ].j_ss = FP_SEG(farstk);
stkswitch[ 0 ].j_sp = FP_OFF(farstk) + stklen;
#elif defined(__GNUG__)
stkswitch[ 0 ].esp = (unsigned int)stack + stklen;
#endif
longjmp(stkswitch, 1);
}
// start task running ...
callTaskMain(CurrTask); // ... and never return
}
//------------------------------------------------------------
// Task::stackok() -- have we blown out bottom of stack?
//------------------------------------------------------------
int
Task::stackok()
{
const int StackChk = 64;
for (int i = 0; i < StackChk; i++)
if (stack[ i ] != stkval)
return 0;
return 1;
}
//------------------------------------------------------------
// Task::suicide() -- become a zombie (scheduler will finish off)
//------------------------------------------------------------
void
Task::suicide()
{
fReady = 0;
fZombie = 1;
if (this == CurrTask) // if we are the current task, then we
suspend(); // aren't on any queues -- just go back
else {
pRunning->Del(this); // we're on one of these queues, so we
pBlocked->Del(this); // just delete from both (one will succeed)
pZombies->Add(this); // put on Zombie queue (scheduler will kill)
}
}
//------------------------------------------------------------
// Task::suspend() -- voluntarily give up control to scheduler
//------------------------------------------------------------
int
Task::suspend()
{
#if CHECK_SUSPEND
if (this != CurrTask)
TaskFatal("suspend task <%s>: not running!", name());
#endif
int n;
if (!(n = setjmp(tskEnv)))
SchResume();
return n;
}
//------------------------------------------------------------
// Task::block() -- block for an event, possibly with
// a timeout period specified in milliseconds...
// since the PC clock ticks 18.2 times a second, the granularity
// of these calls is not too good, but it'll have to do
//------------------------------------------------------------
inline clock_t
msecToTicks(clock_t msec)
{
const clock_t msecPerTick = 10000 / 182;
return (msec + msecPerTick / 2) / msecPerTick;
}
int
Task::block(clock_t msec)
{
if (msec > -1) {
wakeUp = clock() + msecToTicks(msec);
fTimed = 1;
}
fReady = 0;
return suspend() > 0;
}
//------------------------------------------------------------
// Task::unblock() -- return to running queue
//------------------------------------------------------------
void
Task::unblock()
{
fTimed = 0;
fReady = 1;
if (pBlocked->Del(this))
pRunning->Add(this);
}
//------------------------------------------------------------
// Task::maybeWake() -- check the clock, and if the sleeping
// task has slept long enough, return TRUE to unblock
//------------------------------------------------------------
int
Task::maybeWake()
{
return fReady = (fTimed && clock() >= wakeUp);
}